﻿using Sustainalytics.Admin.CRMSynchronization.Utility;
using Sustainalytics.Claims;
using Sustainalytics.DynamicsCRMGateway;
using Sustainalytics.Entities;
using Sustainalytics.Utils;
using Sustainalytics.DataLayer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Utils.Exceptions.Universes;

namespace Sustainalytics.Admin.CRMSynchronization
{
    public class ClaimsSynchronization : IClaimsSync
    {
        private IOperationClaimsRetriever _claimsRetriever;
        private IAccountsRepository _accountsRepository;
        private IDynamicsCrmGateway _crmGateway;
        private IClaimsManager _claimsManager;
        private string[] _permissionsToKeepInRedis;
        private static string _connectionString;

        public ClaimsSynchronization(IDynamicsCrmGateway crmGateway, IClaimsManager claimsManager,
            IOperationClaimsRetriever claimsRetriever, IAccountsRepository accountsRepository, string permissionsToRemainUntouchedInRedis,
            string connectionString)
        {
            _claimsManager = claimsManager;
            _claimsRetriever = claimsRetriever;
            _accountsRepository = accountsRepository;
            _crmGateway = crmGateway;
            if (!String.IsNullOrEmpty(permissionsToRemainUntouchedInRedis))
                _permissionsToKeepInRedis = permissionsToRemainUntouchedInRedis.Split(',');
            else
                _permissionsToKeepInRedis = new string[]{};

            _connectionString = connectionString;
        }

        public void SyncClaims()
        {
            var currentSyncDate = DateTime.UtcNow;
            try
            {
                currentSyncDate += _crmGateway.GetCrmTimeOffset();// translate the local value into the CRM reference system
                CrmSyncEventSource.Log.ClaimsSyncStart(currentSyncDate);

                var claimsMap = _claimsManager.ProcessFeatures();
                var accountMap = _claimsManager.ProcessAccounts();

                var claims = new Entities.Claims() { Id = Guid.NewGuid(), ClaimsMap = claimsMap.ToList(), UserClaims = accountMap.ToList() };

                foreach (var permissionName in _permissionsToKeepInRedis)
                {
                    var hardCodedClaimMap = _claimsRetriever.GetPermissionClaims(permissionName);
                    var claimMap = claims.ClaimsMap.FirstOrDefault(cl => permissionName == cl.Permission);
                    if (claimMap == null)
                    {
                        claims.ClaimsMap.Add(new ClaimMap() { Id=Guid.NewGuid(), Permission = permissionName, Claims = hardCodedClaimMap.ToList() });
                    }
                    else
                    {
                        claimMap.Claims.Clear();
                        claimMap.Claims.AddRange(hardCodedClaimMap);
                    }
                }

                _claimsRetriever.AddClaims(claims);

                SaveAccountPermissions(claims);

                _accountsRepository.SaveLastSync(new Entities.Synchronization { Id = SyncType.Claim, LastSuccessfulSync = currentSyncDate });
                
                CrmSyncEventSource.Log.ClaimsSyncSucceeded(currentSyncDate);
            }
            catch (Exception e)
            {
                CrmSyncEventSource.Log.ClaimsSyncFailed(currentSyncDate,e.GetType().ToString(), e.Message, ExceptionUtility.GetInfo(e));

                throw;
            }
            finally
            {
                CrmSyncEventSource.Log.ClaimsSyncStop(currentSyncDate);
            }
        }

        private static void SaveAccountPermissions(Entities.Claims claims)
        {
            var accountPermissions = new List<AccountPermission>();
            var features = GetFeatureMappings();
            var accountDictionary = GetAccountDictionary();

            foreach (var account in claims.UserClaims)
            {
                AccountPermission accountPermission;

                foreach (var feature in features)
                {
                    accountPermission = new AccountPermission
                    {
                        Id = Guid.NewGuid(),
                        AccountId = account.Id,
                        Type = (PermissionType)Enum.Parse(typeof(PermissionType), feature.Type),
                        Name = feature.Name,
                        IsActive = false,
                        Claims = new List<AccountClaim>()
                    };

                    foreach (var claim in feature.Claims)
                    {
                        var accountClaim = new AccountClaim
                        {
                            Name = claim,
                            IsActive = false
                        };

                        accountPermission.Claims.Add(accountClaim);

                        var userClaim = account.Claims.SingleOrDefault(c => c.Type == claim);

                        if (userClaim == null) continue;

                        if (userClaim.Value != "Allow.All" && userClaim.Value != "Allow.Read") continue;

                        accountClaim.IsActive = true;
                        accountPermission.IsActive = true;
                    }

                    accountPermissions.Add(accountPermission);
                }

                accountPermission = new AccountPermission
                {
                    Id = Guid.NewGuid(),
                    AccountId = account.Id,
                    Type = PermissionType.Features,
                    Name = "Customize ESG Scores",
                    IsActive = false
                };

                var tier = accountDictionary != null && accountDictionary.ContainsKey(account.Id)
                    ? accountDictionary[account.Id]
                    : AccessLevel.Tier3;
                if (tier == AccessLevel.Tier1 || tier == AccessLevel.Tier2)
                {
                    accountPermission.IsActive = true;
                }

                accountPermissions.Add(accountPermission);
            }

            SaveAccountPermissions(accountPermissions);
        }

        private static void SaveAccountPermissions(List<AccountPermission> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<AccountPermission>(_connectionString);

            collection.Database.DropCollection(typeof(AccountPermission).Name);

            collection.InsertMany(documents);
        }


        public static List<FeatureMapping> GetFeatureMappings()
        {
            var collection = MongoFactory.GetCollectionFromDatabase<FeatureMapping>(_connectionString);

            return collection.Find(new BsonDocument()).ToList();
        }

        public static Dictionary<Guid, AccessLevel> GetAccountDictionary()
        {
            var collection = MongoFactory.GetCollectionFromDatabase<Account>(_connectionString);

            var builder = Builders<Account>.Filter;
            var filter = builder.Eq(a => a.Status, Status.Active) &
                         builder.Eq(a => a.HasPlatformAccess, true);

            var accounts = collection.Find(filter)
                .Project(a => new Account
                {
                    Id = a.Id,
                    PlatformTier = a.PlatformTier
                })
                .ToList();

            return accounts.ToDictionary(a => a.Id, a => a.PlatformTier);
        }
    }
}
